package com.iot.spi.core;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.iot.spi.config.IotSpiConfig;
import com.iot.spi.exception.IotSpiException;
import com.iot.spi.response.Resp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;

/**
 * 控制端
 */
@Slf4j
@Component
@AutoConfigureAfter({IotSpiConfig.class})
public class IotClient implements ApplicationListener<ApplicationStartedEvent> {

    private static WebClient webClient;

    @Resource
    private IotSpiConfig config;

    private void checkConfig() {
        if (StrUtil.isBlank(config.getSpi().getHost())){
            throw new IotSpiException("iot host is null");
        }if (StrUtil.isBlank(config.getSpi().getAppKey())){
            throw new IotSpiException("iot appkey is null");
        }if (StrUtil.isBlank(config.getSpi().getAppSecret())){
            throw new IotSpiException("iot AppSecret is null");
        }if (StrUtil.isBlank(config.getEmqx().getHost())){
            throw new IotSpiException("iot emqx host is null");
        }if (StrUtil.isBlank(config.getEmqx().getUserName())){
            throw new IotSpiException("iot emqx userName is null");
        }if (StrUtil.isBlank(config.getEmqx().getPassword())){
            throw new IotSpiException("iot emqx password is null");
        }
    }

    /**
     * 获取加签密钥
     * @return
     */
    public String getTheEncryptedCiphertext() {
        Long t =System.currentTimeMillis();
        String secret = DigestUtil.md5Hex(config.getSpi().getAppKey()+config.getSpi().getAppSecret()+t);
        return config.getSpi().getAppKey()+";"+secret+";"+t;
    }

    /**
     * 发起POST请求的通用方法
     *
     * @param path 请求路径
     * @param request 请求体对象
     * @param <T> 请求体对象的泛型类型
     * @param <R> 响应体对象的泛型类型
     * @return 响应体对象的Mono包装
     */
    private <T, R> Mono<Resp<R>> postRequest(String path, T request,ParameterizedTypeReference<Resp<R>> responseType) {
        return webClient.post()
                .uri(path)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .header("IOT-AUTH", getTheEncryptedCiphertext()) // 这里假设你需要传递一个固定的appKey
                .bodyValue(request)
                .retrieve()
                .bodyToMono(responseType)
                .timeout(Duration.ofSeconds(10))
                .onErrorResume(error -> {
                    // 处理错误
                    return Mono.just(Resp.fail("请求失败")); // 或者返回一个默认值
                });
    }

    public <T, R> Resp<R> execute(WebclientPathSuppost request) {
        if (StrUtil.isBlank(request.getUrlPath())){
            throw new IotSpiException("10011","request {} getUrlPath the function is not implemented",request.getClass().getName());
        }
        ParameterizedTypeReference<Resp<R>> responseType = createResponseTypeReference();
        CompletableFuture<Resp<R>> responseMono = postRequest(request.getUrlPath(), request,responseType).toFuture();
        try {
            return responseMono.get(); // 阻塞等待结果
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    public <T, R> CompletableFuture<Resp<R>> executeAsync(String path, T request) {
        ParameterizedTypeReference<Resp<R>> responseType = createResponseTypeReference();
        return postRequest(path, request, responseType).toFuture();
    }

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        checkConfig();
        try {
            if (config != null && config.getEnabled()){
                //配置固定大小连接池
                ConnectionProvider provider = ConnectionProvider
                        .builder("iot-client-spi")
                        // 等待超时时间
                        .pendingAcquireTimeout(Duration.ofSeconds(30))
                        // 最大连接数
                        .maxConnections(15)
                        // 等待队列大小
                        .pendingAcquireMaxCount(1000)
                        .build();
                HttpClient httpClient = HttpClient.create(provider);
                // 使用Reactor
                webClient = WebClient.builder()
                        .clientConnector(new ReactorClientHttpConnector(httpClient))
                        .baseUrl(config.getSpi().getHost())
                        .build();
            }
            log.info("webclient load OK ... ... ");
        }catch (Exception e){
            throw new IotSpiException("iot init error msg:"+e.getMessage());
        }
    }

    public <T, R> void execute(String path, T request, Consumer<Resp<R>> onSuccess, Consumer<Throwable> onError) {
        ParameterizedTypeReference<Resp<R>> responseType = createResponseTypeReference();
        postRequest(path, request,responseType)
                .subscribe(onSuccess, onError);
    }

    // 私有方法，用于创建 ParameterizedTypeReference
    private <R> ParameterizedTypeReference<Resp<R>> createResponseTypeReference() {
        return new ParameterizedTypeReference<Resp<R>>() {};
    }
}
